﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Manoso.Data.FluentMap
{
    internal static class Tools
    {
        public static Type DataType(this MemberInfo member)
        {
            return member.MemberType == MemberTypes.Field
                ? ((FieldInfo) member).FieldType
                : member.MemberType == MemberTypes.Property ? ((PropertyInfo) member).PropertyType : null;
        }

        public static IEnumerable<MemberInfo> GetDataMembers(this Type type, Func<MemberInfo, bool> where)
        {
            return type.GetMembers(BindingFlags.Public | BindingFlags.Instance)
                    .Where(x => x.MemberType == MemberTypes.Field || x.MemberType == MemberTypes.Property && ((PropertyInfo)x).CanRead && ((PropertyInfo)x).CanWrite)
                    .Where(x => x.DataType() != null)
                    .Where(@where)
                    .ToArray();
        }
        
        public static IList<T> AddMany<T>(this IList<T> list, IEnumerable<T> items)
        {
            foreach (var item in items)
                list.Add(item);

            return list;
        }

        public static string GetMemberName(this LambdaExpression lambda)
        {
            if (lambda == null) return null;
            var expMember = lambda.Body as MemberExpression;
            return expMember == null ? null : expMember.Member.Name;
        }

        public static T GetMemberInfo<T>(this LambdaExpression lambda) where T : MemberInfo
        {
            if (lambda == null) return null;
            var expMember = lambda.Body as MemberExpression;
            return expMember == null ? null : expMember.Member as T;
        }

        public static Type GetMemberType(this MemberInfo member)
        {
            var property = member as PropertyInfo;
            var field = member as FieldInfo;

            return property == null ? field == null ? null : field.FieldType : property.PropertyType;
        }

        public static object GetValue(this MemberInfo member, object obj)
        {
            var property = member as PropertyInfo;
            var field = member as FieldInfo;

            return property == null ? field == null ? null : field.GetValue(obj) : property.GetValue(obj, null);
        }

        public static void SetValue(this MemberInfo member, object obj, object value)
        {
            if (obj == null) return;

            var property = member as PropertyInfo;
            var field = member as FieldInfo;

            if (property != null)
                property.SetValue(obj, value, null);
            else if (field != null)
                field.SetValue(obj, value);
        }

        public static void LoadMap(this IMapTask task, object obj, IEnumerable<IMapProperty> maps)
        {
            if (maps == null)
            {
                if (obj != null)
                    GetObject(task, obj);
            }
            else
            {
                foreach (var map in maps)
                    ReadMapProperty(task, obj, map);
            }
        }

        public static bool IsValid(this IMapTask task, IEnumerable<IMapProperty> ids)
        {
            return ids.All(x => task.HasColumn(x.Column.Alias) && !(task.ReadColumn(x.Column.Alias) is DBNull));
        }

        private static void GetObject(IMapTask task, object obj, string prefix = null)
        {
            var type = obj.GetType();
            foreach (var pi in type.GetProperties())
            {
                try
                {
                    var fieldName = String.IsNullOrWhiteSpace(prefix) ? pi.Name : prefix + pi.Name;
                    var value = task.ReadColumn(fieldName);
                    if (!(value is DBNull))
                        SetProperty(pi.Name, value, obj);
                }
                catch (IndexOutOfRangeException)
                {
                }
            }
        }

        private static void ReadMapProperty(IMapTask task, Object obj, IMapProperty map)
        {
            var column = map.Column.Alias;
            if (map.Set != null)
            {
                if (task.HasColumn(column))
                {
                    var value = task.ReadColumn(column);
                    if (value is DBNull) return;
                    map.Set(obj, task.ReadColumn(column));
                }
            }
            else
                ReadProperty(task, map.PropertyName, column, obj);
        }

        private static void ReadProperty(IMapTask task, string name, string column, object obj)
        {
            if (task.HasColumn(column))
                SetProperty(name, task.ReadColumn(column), obj);
        }

        private static void SetProperty(string name, object value, object obj)
        {
            var type = obj.GetType();
            var property = type.GetProperty(name);

            if (property != null)
            {
                if (!property.CanWrite || value is DBNull) return;

                property.SetValue(obj, value, null);
            }
            else
            {
                var field = type.GetField(name);
                if (field == null) return;

                field.SetValue(obj, value);
            }
        }
    }
}

